
#include "OpenclObj.hpp"

void OpenclObj::GetPlatformID(int n)
{
    cl_int err;
    cl_uint num_platform;
    err = clGetPlatformIDs(0, NULL, &num_platform);
    assert(err == CL_SUCCESS);

    cl_platform_id *platform_idp = new cl_platform_id[num_platform];
    err = clGetPlatformIDs(num_platform, platform_idp, NULL);
    assert(err == CL_SUCCESS);

    if (num_platform < n)
    {
        std::cout << "err: this pc only have " << num_platform << " platforms\n";
        exit(EXIT_FAILURE);
    }
    platform = platform_idp[n - 1];

    char name[256];
    size_t info_size;
    err = clGetPlatformInfo(platform, CL_PLATFORM_NAME, sizeof(name), name, NULL);
    assert(err == CL_SUCCESS);
    // err = clGetPlatformInfo(platform, CL_PLATFORM_NAME, info_size, name, NULL);
    std::cout << "platform name: " << name << "\n";
}

void OpenclObj::CreateContextFromType(cl_device_type device_type)
{
    cl_context_properties properties[] = {
        CL_CONTEXT_PLATFORM,
        (cl_context_properties)platform,
        0};

    cl_int err;
    context = clCreateContextFromType(properties, device_type, NULL, NULL, &err);
    assert(err == CL_SUCCESS);
}

void OpenclObj::CreateCommandQueue()
{
    cl_int err;

    size_t device_buffer_size;
    err = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &device_buffer_size);
    assert(err == CL_SUCCESS);
    // 第三个参数的大小必须大于或等于第四个参数， 没办法的事情
    cl_device_id *device_idp{new cl_device_id[device_buffer_size / sizeof(cl_device_id)]};
    err = clGetContextInfo(context, CL_CONTEXT_DEVICES, device_buffer_size, device_idp, NULL);
    assert(err == CL_SUCCESS);

    cl_queue_properties properties[] = {CL_QUEUE_PROPERTIES, CL_QUEUE_PROFILING_ENABLE, 0};
    // 使用上下文关联的设备中的第一个
    command_queue =
        clCreateCommandQueueWithProperties(context, device_idp[0], properties, &err);
    assert(err == CL_SUCCESS);

    device = device_idp[0];
    delete[] device_idp;
    char name[256];
    err = clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(name), name, NULL);
    assert(err == CL_SUCCESS);
    std::cout << "device name: " << name << "\n";
}

void OpenclObj::CreateProgram(const char *kernel_file, const char *options)
{
    cl_int err;
    std::ifstream clfile{kernel_file, std::ios_base::in};
    if (!clfile)
    {
        std::cerr << "open file error: " << kernel_file << "\n";
        exit(EXIT_FAILURE);
    }
    std::ostringstream oss;
    oss << clfile.rdbuf();
    std::string src{oss.str()};
    // 得到内核文件源代码
    const char *srcStr{src.c_str()};

    program = clCreateProgramWithSource(context, 1, (const char **)&srcStr, NULL, &err);
    assert(err == CL_SUCCESS);

    // std::string options{};
    err = clBuildProgram(program, 1, &device, options, NULL, NULL);
    if (err != CL_SUCCESS)
    {
        // 输出编译错误信息
        size_t log_size;
        err = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
        assert(err == CL_SUCCESS);

        char *err_log = new char[log_size + 1];
        err = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, err_log, NULL);
        assert(err == CL_SUCCESS);
        err_log[log_size] = '\0';
        std::cerr << "build program fail\n";
        std::cerr << err_log;
        clReleaseProgram(program);
        delete[] err_log;
        exit(EXIT_FAILURE);
    }
}

void OpenclObj::CreateKernelsInProgram()
{
    cl_int err;
    cl_uint num_kernel;
    err = clCreateKernelsInProgram(program, 0, NULL, &num_kernel);
    assert(err == CL_SUCCESS);
    std::cout << "there have " << num_kernel << " kernels\n";

    kernels.second = static_cast<int>(num_kernel);
    kernels.first = new cl_kernel[num_kernel];
    err = clCreateKernelsInProgram(program, kernels.second, kernels.first, NULL);
    assert(err == CL_SUCCESS);
}

cl_kernel OpenclObj::GetKernel(const std::string name) const
{
    cl_int err;
    char kernel_name[128];
    for (int i = 0; i < kernels.second; ++i)
    {
        err = clGetKernelInfo(kernels.first[i], CL_KERNEL_FUNCTION_NAME,
                              sizeof(kernel_name), kernel_name, NULL);
        assert(err == CL_SUCCESS);
        if (name == std::string{kernel_name})
        {
            return kernels.first[i];
        }
    }
    std::cerr << "cannot find the kernel, kernel function_name = "
              << name << "\n";
    exit(EXIT_FAILURE);
}

OpenclObj::~OpenclObj()
{
    if (kernels.first)
    {
        for (int i = 0; i < kernels.second; ++i)
            clReleaseKernel(kernels.first[i]);
    }
    if (program)
        clReleaseProgram(program);
    if (command_queue)
        clReleaseCommandQueue(command_queue);
    if (context)
        clReleaseContext(context);
}